
	/***********************************************************************************************
	  
		Class: jsl.EventMap
					
		Methods:
			map				EventMap					()														- sid: 3.0
			map				addEvent					(id, action*)									- sid: 3.0
			map				addEvents					(ids..)												- sid: 3.0
			b					isEvent						(id, status*)									- sid: 3.0
			data			throwEvent				(id, e*)											- sid: 3.0

			map				removeEvent				(id)													- sid: 3.0
			map				removeEvents			(ids..*)											- sid: 3.0

			map				setEventAction		(id, action)									- sid: 3.0
			action		getEventAction		(id)													- sid: 3.0		
			
			map				enableEvent				(id)													- sid: 3.0
			map				disableEvent			(id)													- sid: 3.0
			map				enableEvents			(ids..*)											- sid: 3.0
			map				disableEvents			(ids..*)											- sid: 3.0

			b					hasListeners			(event*, status*)							- sid: 3.0
			b					isListener				(id, status*)									- sid: 3.0

			map				addListener				(event, action/data)					- sid: 3.0
			map				setListener				(id, data)										- sid: 3.0
			data			getListener				(id)													- sid: 3.0

			map				enableListener		(id)													- sid: 3.0
			map				disableListener		(id)													- sid: 3.0
			map				enableListeners		(ids..)												- sid: 3.0
			map				disableListeners	(ids..)												- sid: 3.0
			
			map				removeListener		(id)													- sid: 3.0
			map				removeListeners		(ids..)												- sid: 3.0
			
		Note:	
			data:				o{ action, priority, enabled, id*, event* }
			priority:		from Infinity(high) to -Infinity(low)
			throwEvent:	the event object e is shared through all listeners and allow comunication
												
	************************************************************************************************/

	//-------------------------------------------------------------------------------
	jsl.Class("jsl.EventMap",
		{
			//Private vars
			//-------------------------------------------------------------------------------
			__events:	null,
						
			//-------------------------------------------------------------------------------
			EventMap: function() {//@d[							
				if(arguments.length) throw jsl.ParamError()//@d]					
				this.__events	= []
			},
			//-------------------------------------------------------------------------------
			addEvent: function(id, action) {
				var e
				//@d[
				if(!arguments.length || arguments.length > 2)	throw jsl.ParamError()	
				jsl.validate(id, "s+", "id")				
				if(this["__e_" + id])				throw jsl.ParamError("AlreadyDefined:id")
				if(arguments.length === 2)	jsl.validate(action, "f", "action")//@d]
				
				e													= []
				e.id											= id
				e.enabled									= true
				e.nls											= 0
				e.obj											= this
				e.idx											= this.__events.length
				e.action									= action || null
								
				this["__e_" + id]					= e
				this.__events["e:" + id]	= e
				this.__events.push(e)	
													
				return this
			},
			//-------------------------------------------------------------------------------
			addEvents: function() {//@d[
				if(!arguments.length)	throw jsl.ParamError()//@d]
				
				for(var a = 0, len = arguments.length; a < len; a++) 
					this.addEvent(arguments[a])
					
				return this
			},
			//-------------------------------------------------------------------------------
			isEvent: function(id, status) {
				var v
				//@d[
				if(!arguments.length || arguments.length > 2)	throw jsl.ParamError()
				jsl.validate(id, "s+", "id")				
				if(arguments.length === 2) jsl.validate(status, "s+(enabled,disabled)", "status")//@d]
							
				v = this["__e_" + id]
								
				if(v && status) 
					v = status === "enabled" ? v.enabled : !v.enabled
					
				return !!v
			},
			//-----------------------------------------------------------------------------------------------
			throwEvent: function(id, e) {
				var map, rv, v, evt, lid, a, l, len, data, obj, edata

				//@d[
				if(!arguments.length || arguments.length > 2)	throw jsl.ParamError()
				jsl.validate(id, "s+", "id")				

				if(!this["__e_" + id]) 			throw jsl.ParamError("NotDefined:id")				
				if(arguments.length === 2) 	jsl.validate(e, "o", "e")//@d]

				rv 		= 0
				edata	= this["__e_" + id]
											
				if(edata.enabled) {								
				
					map 				= this
					evt					= e && e instanceof jsl.Event ? e : (e ? new jsl.Event(e) : new jsl.Event)
					evt.eventId	= id + ""											
					lid					= "on" + id.charAt(0).toUpperCase() + id.slice(1)
					obj					= edata.obj	
							
					do {
						//must be a EventMap object constructed
						if(map.__events) {
							
							if(map[lid] != null) {
								
								//@d[
								if(!jsl.isFunction(map[lid]))	throw jsl.TypeError("BadType", [ lid , "function" ])//@d]
								
								//a listener is called on the map where was defined
								try 			{ rv |= +map[lid].call(this, evt) || 0																	}
								catch(e)	{	throw jsl.Error(e).trace(this.getName() + "." + lid + "(e)", [ evt ])	}
								
								if(rv & 1) break
							}
							
							data = map.__events["e:" + id]
							
							if(data && data.nls) 					 							
								for(a = 0, len = data.length; a < len ; a++) {						
									
									l = data[a]
									
									if(l.enabled) {
										try 			{ rv |= +l.action.call(this, evt) || 0 																						}
										catch(e) 	{ throw jsl.Error(e).trace(this.getName() + "." + (l.id || lid) + "(e)", [ evt ])	}
										
										if(rv & 1) break 
									}
								}				
											
							if(map === obj || (rv & 1)) 
								break
						}
																												
						map = map.__proto							
					} while(map)
										
					if(!(rv & 2) && edata.action)
						edata.action.call(this, evt)					
				}
								
				return {
					STOP_LISTENERS:  rv & 1,
					STOP_DEFAULT: 	 rv & 2,
					STOP_EVENTS: 		 rv & 4,
					STOP: 					(rv & 7) === 7,
					OK:							(rv & 7) === 0
				}
			},
			//-------------------------------------------------------------------------------
			removeEvent: function(id) {
				var e, map, a, obj
				//@d[
				if(arguments.length !== 1)	throw jsl.ParamError()
				jsl.validate(id, "s+", "id")
				if(!this["__e_" + id])	throw jsl.ParamError("NotDefined:id")//@d]
				
				e 	= this["__e_" + id]
				map = this
				obj	= e.obj
				
				delete obj["__e_" + id]
				
				do {
					
					//if an object in the prototype chain is not an EventMap __events is inherited to null
					//not use hasOwnProperty, a listener is always executed once because in the iterator loop of the
					//chain the __events is seen as null
					if(map.__events) {
						
						e = map.__events["e:" + id]
												
						if(e) {
							delete map.__events["e:" + id]			
							map.__events.splice(e.idx, 1)							
						}

						if(map === obj)
							break			
					}
														
					map = map.__proto
				} while(map)			
										
				return this
			},
			//-------------------------------------------------------------------------------
			removeEvents: function() {
				var args, map, a , l
				
				if(arguments.length) {

					args = arguments

					for(a = 0, l = args.length; a < l; a++) {
						//@d[
						jsl.validate(args[a], "s+", "ids[" + a + "]")
						if(!this["__e_" + args[a]])	throw jsl.ParamError("NotDefined", [ "ids[" + a + "]" ])//@d]				
						this.removeEvent(args[a])
					}
				}
				else {
				
					map = this
					
					do {
						
						if(map.__events) {
							
							for(a = 0, l = map.__events.length; a < l; a++)
								if(map.__events[a].obj === map)
									delete map["__e_" + map.__events[a].id]
								
							map.__events = [] 
						}									
						map = map.__proto
					} while(map)								
				}
							
				return this
			},			
			//-------------------------------------------------------------------------------
			setEventAction: function(id, action) {
				//@d[
				if(arguments.length !== 2)	throw jsl.ParamError()
				jsl.validate(id, "s+", "id")
				if(!this["__e_" + id])	throw jsl.ParamError("NotDefined:id")
				if(action != null)			jsl.validate(action, "f", "action")//@d]
				
				//we must overwrite a prototype default action -> no delete
				this["__e_" + id].action = action || null				
				return this
			},
			//-------------------------------------------------------------------------------
			getEventAction: function(id) {
				//@d[
				if(arguments.length !== 1)	throw jsl.ParamError()
				jsl.validate(id, "s+", "id")
				if(!this["__e_" + id])	throw jsl.ParamError("NotDefined:id")//@d]

				return this["__e_" + id].action
			},
			//-------------------------------------------------------------------------------
			enableEvent: function(id) {
				var v
				//@d[
				if(arguments.length !== 1)	throw jsl.ParamError()
				jsl.validate(id, "s+", "id")				
				if(!this["__e_" + id])			throw jsl.ParamError("NotDefined:id")//@d]
				
				this["__e_" + id].enabled = true								
				return this
			},
			//-------------------------------------------------------------------------------
			disableEvent: function(id) {
				var v
				//@d[
				if(arguments.length !== 1)	throw jsl.ParamError()
				jsl.validate(id, "s+", "id")				
				if(!this["__e_" + id])			throw jsl.ParamError("NotDefined:id")//@d]
				
				this["__e_" + id].enabled = false								
				return this
			},
			//-------------------------------------------------------------------------------
			enableEvents: function() {
				var map, es, a, len
				
				if(!arguments.length) {
				
					es	= this.__events
					map = this
							
					do {
						
						if(map.__events)	
							es = es.concat(map.__events)
						
						map = map.__proto
					}	while(map)
									
					es = jsl.__mlang.array_unique.call(es) 

					for(a = 0, len = es.length; a < len; a++) 
						this.enableEvent(es[a].id)				
				}
				else 
					for(a = 0, len = arguments.length; a < len; a++) 
						this.enableEvent(arguments[a])
									
				return this
			},
			//-------------------------------------------------------------------------------
			disableEvents: function() {
				var map, es, a, len
				
				if(!arguments.length) {
				
					es	= this.__events
					map = this
					
					do {
						
						if(map.__events)	
							es = es.concat(map.__events)
						
						map = map.__proto
					}	while(map)
									
					es = jsl.__mlang.array_unique.call(es) 

					for(a = 0, len = es.length; a < len; a++) 
						this.disableEvent(es[a].id)				
				}
				else 
					for(a = 0, len = arguments.length; a < len; a++) 
						this.disableEvent(arguments[a])
									
				return this
			},
			//-------------------------------------------------------------------------------
			hasListeners: function(event, status) {
				var l, lid, e, b, a, es, map, ids
				
				//@d[
				if(arguments.length > 2)		throw jsl.ParamError()
				if(arguments.length) 				jsl.validate(event	, "s+", "event")
				if(arguments.length === 2) 	jsl.validate(status	, "s+(enabled,disabled)", "status")//@d]
				
				map = this
				
				if(event) {
					//@d[
					if(!this["__e_" + event])	throw jsl.ParamError("NotDefined:event")//@d]

					lid	= "on" + event.charAt(0).toUpperCase() + event.slice(1)
					
					do {
						
						if(map.__events) {

							l = map[lid]
							e	= map.__events["e:" + event]
							//@d[
							if(l != null && !jsl.isFunction(l))	throw jsl.TypeError("BadType", [ lid , "function" ])//@d]
							
							if(l || e) 
								if(status)	
									if(status === "disabled")	b = e && e.nls !== e.length  
									else											b = l || e && e.nls 
								else 												b = l || e && e.length	
						}
						map = map.__proto						
					} while(map && !b)									
				}
				else 	{			
				
					ids = []
					
					do {
						if(map.__events) {
											
							es = map.__events

							for(a = es.length - 1; a >= 0 && !b; a--) 
								if(es[a].length) 				b = true
								else if(!ids[es[a].id])	ids.push(es[a].id)
						}							

						map = map.__proto
					} while(map && !b)					
										
					for(a = ids.length - 1; a >= 0 && !b; a--) 
						if(this["on" + es[a].id.charAt(0).toUpperCase() + es[a].id.slice(1)] != null)
							b = true
				}
				
				return !!b
			},
			//-------------------------------------------------------------------------------
			isListener: function(id, status) {
	  		var l
				
				//@d[
				if(!arguments.length && arguments.length > 2)	throw jsl.ParamError()
				jsl.validate(id, "s+", "id")
				if(arguments.length === 2) jsl.validate(status, "s+(enabled,disabled)", "status")//@d]
				
				l = this["__l_" + id]
					
				if(l && status)
					l = status === "enabled" ? l.enabled : !l.enabled

				return !!l
		  },
			//-------------------------------------------------------------------------------
			addListener: function(event, data) {
				var e, l
				//@d[
				if(arguments.length !== 2)	throw jsl.ParamError()
				jsl.validate(event, "s+", "event")								
				if(!this["__e_" + event])	throw jsl.ParamError("NotDefined:event")
				jsl.validate(data, "o", "data")//@d]
				
				e = this.__events["e:" + event]
				
				if(!e) {					
					e 													= []
					e.id												= event
					e.enabled 									= true
					e.nls 											= 0
					e.idx												= this.__events.length
					e.obj												= this
					this.__events["e:" + event] = e
					this.__events.push(e)
				}
				
				if(!jsl.isFunction(data)) {
          //@d[
					if(data.id != null) {
						jsl.validate(data.id, "s+", "data.id")		
						if(this["__l_" + data.id])	throw jsl.ParamError("AlreadyDefined:data.id")
					}
					
          if(data.priority 	!= null)	jsl.validate(data.priority, "N", "data.priority")
	        if(data.enabled 	!= null)	jsl.validate(data.enabled	, "b", "data.enabled")
				
					jsl.validate(data.action, "f", "data.action")//@d]
				
					l	= {
	              event:    event 	+ ""       														,
								id:				data.id != null ? data.id + ""	: null				,
								action: 	data.action																		,
								priority: +data.priority 	|| 0													,
								enabled:	data.enabled != null ? !!+data.enabled	: true,
								eobj:			e
							}
        }
				else
					l = {
								event: 		event + "",
								id:				null,
								action:		data,
								priority:	0,
								enabled:	true,
								eobj:			e
							}
				
				l.priority === -Infinity ? e.unshift(l) : e.push(l)
				e.sort(function(a, b) { return b.priority - a.priority })
				
				if(l.enabled)	
					e.nls++
				
				if(l.id) {
					this["__l_" + l.id] 				= l
					this.__events["l:" + l.id] 	= l
				}
				
				return this
			},
			//-----------------------------------------------------------------------------------------------
			setListener: function(id, data) {
				var l
				//@d[
				if(arguments.length !== 2)	throw jsl.ParamError()
				jsl.validate(id, "s+", "id")//@d]
						
				l = this["__l_" + id]
				//@d[					
				if(!l)	throw jsl.ParamError("NotDefined:id")

				jsl.validate(data, "o", "data")

				if(data.event != null) {
					jsl.validate(data.event, "s+", "data.event")
					if(!this["__e_" + data.event])	throw jsl.ParamError("NotDefined:data.event")
				}
				
				if(data.id				!= null)	jsl.validate(data.id			, "s+", "data.id")
				if(data.action 		!= null)	jsl.validate(data.action	, "f"	, "data.action")
				if(data.priority 	!= null)	jsl.validate(data.priority, "N"	, "data.priority")
				if(data.enabled 	!= null)	jsl.validate(data.enabled	, "b"	, "data.enabled")
				//@d]
				this.removeListener(id)
				
				l.action		= data.action	|| l.action
				l.id 				=	data.id 			!= null ? data.id 		+ "" 	: l.id
				l.event			=	data.event 		!= null ? data.event	+ "" 	: l.event
				l.priority	= data.priority	!= null ? +data.priority		: l.priority
				l.enabled		=	data.enabled 	!= null ? !!+data.enabled 	: l.enabled
								
				this.addListener(l.event, l)
					
				return this
			},
			//-------------------------------------------------------------------------------
			getListener: function(id) {
	  		var l, rv
				//@d[
				if(arguments.length !== 1)	throw jsl.ParamError()
				jsl.validate(id, "s+", "id")//@d]
				
				l = this["__l_" + id]//@d[					
				if(!l)	throw jsl.ParamError("NotDefined:id")//@d]

				rv					= {}
				rv.id 			= id
				rv.event		= l.event
				rv.action		= l.action
				rv.enabled	= l.enabled
				rv.priority	= l.priority

				return rv
		  },
			//-------------------------------------------------------------------------------
			enableListener: function(id) {
				var l
				//@d[
				if(arguments.length !== 1)	throw jsl.ParamError()
				jsl.validate(id, "s+", "id")//@d]
				
				l = this["__l_" + id]//@d[					
				if(!l)	throw jsl.ParamError("NotDefined:id")//@d]
				
				if(!l.enabled) {
					l.enabled = true
					l.eobj.nls++
				}
				return this
		  },
			//-------------------------------------------------------------------------------
			disableListener: function(id) {
				var l
				//@d[
				if(arguments.length !== 1)	throw jsl.ParamError()
				jsl.validate(id, "s+", "id")//@d]
				
				l = this["__l_" + id]//@d[					
				if(!l)	throw jsl.ParamError("NotDefined:id")//@d]
								
				if(l.enabled) {
					l.enabled = false
					l.eobj.nls--
				}
				return this
		  },
			//-------------------------------------------------------------------------------
			enableListeners: function() {
				//@d[
				if(!arguments.length)	throw jsl.ParamError()//@d]
								
				for(var a = 0, len = arguments.length; a < len; a++) 
					this.enableListener(arguments[a])
				
				return this
		  },
			//-------------------------------------------------------------------------------
			disableListeners: function() {
				//@d[
				if(!arguments.length)	throw jsl.ParamError()//@d]
								
				for(var a = 0, len = arguments.length; a < len; a++) 
					this.disableListener(arguments[a])
				
				return this
		  },
			//-------------------------------------------------------------------------------
			removeListener: function(id) {
				var l, e, obj, a
				//@d[
				if(arguments.length !== 1)	throw jsl.ParamError()
				jsl.validate(id, "s+", "id")//@d]
				
				l = this["__l_" + id]//@d[					
				if(!l)	throw jsl.ParamError("NotDefined:id")//@d]
				
				e 	= l.eobj
				obj	= e.obj				
				if(l.enabled) e.nls--
				
				delete obj["__l_" + id]
				delete obj.__events["l:" + id]
				
				for(a = 0, l = e.length; a < l; a++)
					if(e[a].id == id) {
						e.splice(a, 1)
						break
					} 
				
				return this
		  },			
			//-------------------------------------------------------------------------------
			removeListeners: function() {
				//@d[
				if(!arguments.length)	throw jsl.ParamError()//@d]
								
				for(var a = 0, len = arguments.length; a < len; a++) 
					this.removeListener(arguments[a])
				
				return this
			}
		})
